package top.jiangqiang.qianyi.business.service.impl;

import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.dromara.hutool.core.codec.binary.HexUtil;
import org.dromara.hutool.core.io.file.FileNameUtil;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import top.jiangqiang.qianyi.business.model.request.SysFileListRequest;
import top.jiangqiang.qianyi.business.model.request.SysFilePageRequest;
import top.jiangqiang.qianyi.business.model.request.SysFileSaveRequest;
import top.jiangqiang.qianyi.business.model.request.SysFileUpdateRequest;
import top.jiangqiang.qianyi.business.model.response.ProgressResponse;
import top.jiangqiang.qianyi.business.model.response.SysFileDetailResponse;
import top.jiangqiang.qianyi.business.model.response.SysFileListResponse;
import top.jiangqiang.qianyi.business.model.response.SysFilePageResponse;
import top.jiangqiang.qianyi.business.service.SysFileService;
import top.jiangqiang.qianyi.common.auth.SessionHolder;
import top.jiangqiang.qianyi.common.base.model.response.PageResult;
import top.jiangqiang.qianyi.common.base.model.response.Result;
import top.jiangqiang.qianyi.common.base.service.impl.BaseServiceImpl;
import top.jiangqiang.qianyi.common.constants.http.Error500Status;
import top.jiangqiang.qianyi.common.exception.Error500Exception;
import top.jiangqiang.qianyi.common.storage.StorageService;
import top.jiangqiang.qianyi.data.model.entity.SysFile;
import top.jiangqiang.qianyi.data.service.SysFileDbService;

import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.List;

/**
 * <p>
 * 系统文件表 服务实现类
 * </p>
 *
 * @author JiangQiang
 * @since 2024-07-19
 */
@Service
@RequiredArgsConstructor
public class SysFileServiceImpl extends BaseServiceImpl<SysFileDbService, SysFile> implements SysFileService {
    private final StorageService storageService;

    /**
     * @param fileKey 如果以/开头，则去除/
     * @return
     */
    private static String processingFileKey(@NotNull String fileKey) {
        if (fileKey.startsWith("/")) {
            return fileKey.substring(1);
        } else {
            return fileKey;
        }
    }

    @Override
    @SneakyThrows
    public SseEmitter upload(MultipartFile multipartFile) {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        try (var inputStream = multipartFile.getInputStream(); DigestInputStream digestInputStream = new DigestInputStream(inputStream, digest)) {
            SseEmitter emitter = new SseEmitter(0L);
            SysFile sysFile = new SysFile();
            Long userId = SessionHolder.currentUser().getUserId();
            //由于上传可能是异步的，存数据库时不一定拿得到用户ID，提前到这里获取
            sysFile.setCreateBy(userId);
            sysFile.setUpdateBy(userId);
            storageService.upload(multipartFile.getOriginalFilename(), digestInputStream, progressContext -> {
                try {
                    ProgressResponse progressResponse = typeConverter.convert(progressContext, ProgressResponse.class);
                    progressResponse.setUrl(getUrl(progressResponse.getFileKey()));
                    emitter.send(SseEmitter.event().data(Result.ok(progressResponse)));
                    if (progressContext.getComplete()) {
                        String sha256 = HexUtil.encodeStr(digest.digest());
                        sysFile.setFileKey(progressResponse.getFileKey());
                        sysFile.setName(multipartFile.getOriginalFilename());
                        sysFile.setStorageId(storageService.getStorageConfig().id());
                        sysFile.setStorageConfig(storageService.getStorageConfig());
                        sysFile.setSha256(sha256);
                        sysFile.setContentType(multipartFile.getContentType());
                        sysFile.setExtName(FileNameUtil.extName(multipartFile.getOriginalFilename()));
                        sysFile.setSize(multipartFile.getSize());
                        save(sysFile);
                        emitter.complete();
                    }
                } catch (IOException e) {
                    emitter.completeWithError(e);
                }
            });
            return emitter;
        } catch (Exception e) {
            throw new Error500Exception(Error500Status.INTERNAL_UPLOAD_FAIL, e);
        }
    }

    @Override
    public void download(String fileKey, HttpServletResponse response) {
        try {
            //此流关闭后会导致接口拿不到错误响应信息，所以这里不应该关闭
            var outputStream = response.getOutputStream();
            storageService.download(processingFileKey(fileKey), outputStream);
        } catch (IOException e) {
            throw new Error500Exception(Error500Status.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    public byte[] download(String fileKey) {
        return storageService.download(processingFileKey(fileKey));
    }

    @Override
    public String getUrl(String fileKey) {
        return storageService.getUrl(processingFileKey(fileKey));
    }

    @Override
    public void delete(String fileKey) {
        storageService.delete(processingFileKey(fileKey));
    }

    @Override
    public Result<PageResult<SysFilePageResponse>> page(SysFilePageRequest request) {
        PageResult<SysFilePageResponse> page = request.toPage();
        return Result.ok(getBaseDbService().lambdaJoinWrapper().page(page, SysFilePageResponse.class));
    }

    @Override
    public Result<List<SysFileListResponse>> list(SysFileListRequest request) {
        return Result.ok(getBaseDbService().lambdaJoinWrapper().list(SysFileListResponse.class));
    }

    @Override
    public Result<Void> add(SysFileSaveRequest request) {
        SysFile sysFile = typeConverter.convert(request, SysFile.class);
        save(sysFile);
        return Result.ok();
    }

    @Override
    public Result<Void> update(SysFileUpdateRequest request) {
        SysFile sysFile = typeConverter.convert(request, SysFile.class);
        updateById(sysFile);
        return Result.ok();
    }

    @Override
    public Result<SysFileDetailResponse> detail(Long id) {
        return Result.ok(getById(id, SysFileDetailResponse.class));
    }

    @Override
    public Result<Void> delete(Long id) {
        removeById(id);
        return Result.ok();
    }

}